\chapter{Исключения}

\section{Лекция 27.03.2012. Исключения}

\subsection{Цели}

Во-первых, мы рассматриваем только ошибки не связанные с логикой программы (например, неправильный ввод или винчестер отлетел и тд).
Во-вторых, ошибки обрабатываюстя не только, чтобы предотвратить падение программы, но, и если это не удалось, донести до пользователя
проблему.

\subsection{Стратегии обработки ошибок}

Самый простой подход к нотификации об ошибках:

\begin{lstlisting}
bool do_smth();
\end{lstlisting}

в этом случае, если вернется true, значит функция отработала нормально, если false, то произошла ошибка. У этого подхожа есть следующие
проблемы:
\begin{itemize}
\item Мы узнаем об ошибке, но не знаем, что именно случилось (это можно исправить, например, заменив bool на int)

\item Если функция должна что-то возвращать, то ей придется делать это через список аргументов

\item Если вся обработка ошибок построена на таком принципе, то проверка результата выполнения всех функций может выглядеть достаточно
страшно, а кроме того в силу утомительности его написания потенциально бажным.
\end{itemize}

Следующий подход (errno) применяется активно в си. Вместо возврата сообщения об ошибке в функции, мы используем глобальную переменную,
тогда очевидно мы решаем проблему с интерфейсом функции (в нем нет отвлекающих от логики вещей). Этот подход достаточно привлекателен
своей простотой, а кроме того, добавив некоторые библиотечные функции мы сможем, например, узнать не только код ошибки, но и автоматически
получить некоторое представление (строковое как правило) для ошибки.

Недостаток такого подхожда очевиден, нужно не забыть вовремя сбросить значение глобальной переменной, но кроме того есть еще
и некоторые проблемы с несколькими потоками выполнения и динамической линковкой, но они решаются. Ну и наконец беда такого подхода еще и
в том, что мы используем глобальную переменную, а это как известно печально.

Переходя к С++ с первым и третьим подходами возникают проблемы, так как там появляются функции с предопределенной сигнатурой, например,
конструкторы и операторы, как быть в этом случае? Тут мы должны развить наши подходы с возвратами до исключений. Кроме того использование
исключений позволяет избавится от необходимости проверять возврат при вызове каждой функции и при этом не получим наведенной ошибки. То есть
они исключения позволяют совместить преимущества подходов с возвратом значения и "удобство" подхода errno, не добавляя новых проблем.

\subsection{Бросание и отлов исключения}

В случае бросания исключения весь код с текущего места, до места отлова исключения выполнен не будет (если его нигде не ловить, то
программа упадет). НО! Совершаются еще кое-какие манипуляции, например, происходит процесс сворачивания стека, все что было выделенно
в стеке удалится (с вызовом деструктора, что очень хорошо).

Синтаксически выброс исключения выглядит просто:
\begin{lstlisting}
throw something;
\end{lstlisting}

в качестве somethig может выступать, все что угодно (строка, число или класс).

Отлов исключения может происходить выше по стеку вызовов или в том же месте (хотя это и странно)
\begin{lstlisting}
try
{
	throw something;
}
catch (int i)
{
	...
}
catch (double d)
{
	throw d;
}
catch (...)
{
	throw;
}
\end{lstlisting}

try ... catch обозначает блок отлова исключений, параметром catch является тип исключения, приведения при выборе нужного catch не происходит,
выберется первый точно соответствующий тип, и это разумно, так как в случае использования классов в качестве объекта исключения мы можем опять
получить исключение при попытке привдения типа.

catch (...) - отлавливает все исключения (это согласуется со смыслом ...)

Внутри блока catch мы можем продублировать исключение ( смотри catch (double d) и catch (...) )

Положение объектов исключения никак не гарантируется стандартами, но тем не менее ловить исключение по ссылке возможно, и более того настоятельно
рекомендуется ловить объекты исключения по константной ссылке.

\subsection{Объекты исключений}

В качестве объектов исключений стоит использовать классы, по нескольким причинам:
\begin{itemize}
\item Классы более информативны

\item При выборе catch блока приведение типов не происходит, за исключением случая приведения к родителю, что полезно, например,
если все исключения являются наследниками одного базового класса, то отловив исключние базового класса мы отловим все исключения.
\end{itemize}

В качестве базовго класса исключения в C++ присутствует std::exception, при наследовании от него нужно переобпределить одну
функцию (но можно отнаследоваться и от более конкретного класса исключения):
\begin{lstlisting}
char const * what();
\end{lstlisting}

\subsection{Интеграция исключений в C++}

\begin{lstlisting}
A * p = new A();
A[] a = new A[100];
\end{lstlisting}

в примерах выше, в случае исключения в конструкторе A, память выделенная под объекты будет освобождена, а для объектов, которые были
созданы (внутри конструктора, например, или, если при создании массива на 30 элементе произошло исключение, то 29 предыдущих обхектов
уже сконструированы) будет вызван деструктор, и это хорошо.

Одновременное существование исключений невозможно, т. е. например, если в процессе свертки стека после исключения произойдет новое исключение,
то программа гарантированно падает, поэтому, например,

\paragraph{деструкторы не должны брость исключения}

, вообще исключения в деструкторе приводят к неопределенному поведению.

\subsection{Исключения в конструкторе}

Сообщить об ошибке в конструкторе можно только с помощью исключения, поэтому рассмотрим подробнее как это происходит. Объект некоторого типа
является созданным, только после выхода из конструктора, поэтому при исключении в теле конструктора, деструктор для него вызван не будет.
Кроме того, деструктор будет вызван для всех созданных в теле объектов.

Не используйте внутри конструктора raw указатели, пример:

\begin{lstlisting}
class A
{
	D *a;
	B b;
	C c;
	
	A()
	: a(new D()), b(), c()
	{
		...
	}
};
\end{lstlisting}
если исключение произошло в теле конструктора, то память, на которую указывает a, освобождена не будет, по сему используйте умные указатели.

Что сделать, если исключение произвошло, например, в списке инициализации и мы хотим его отловить? Для таких вещей существует специальный
синтаксис для обработки исключений:

\begin{lstlisting}
A ()
: try a(new D()), b(), c()
{
}
catch (...)
{
	...
}
\end{lstlisting}

Но даже если исключние будет обработано, оно всеравно пробросится выше, а объект будет считаться несозданным.

\subsection{Спецификация исключений}

В С++ есть способ указать, какие исключения бросает функция, но эта возможность считается устаревшей и будет удалена (когда-нибудь).